# Copyright (c) 2007-2009 The PyAMF Project.
# See LICENSE for details.

"""
Socket example server using Twisted.

@see: U{Wiki page for this example<http://pyamf.org/wiki/BinarySocket>}

@since: 0.1.0
"""

try:
    import twisted
except ImportError:
    print "This examples requires the Twisted framework. Download it from http://twistedmatrix.com"
    raise SystemExit

from twisted.internet.protocol import Protocol, Factory
from twisted.internet import reactor

from datetime import datetime
import pyamf
import urllib

class MServeurProtocol(Protocol):
	encoding = pyamf.AMF3
	timeout = 4

	def __init__(self):
		self.decoder=None
		self.action=Actions(self)
		self.timeout_deferred=None
		self.identified=False
		self.rooms=None #MPrivateRooms
		self.idx=None
	def close(self):
		self.transport.loseConnection()
		
	def connectionLost(self, reason):
		if(not self.rooms is None):
			if(self.identified):
				for p in self.rooms.rooms:
					self.action.leave(p)
		self.factory.clients.disconnect(self)
		self.identified=False
		self.end_timeout()
		print "connexion perdue "+str(self.factory.number_of_connections-1)
		Protocol.connectionLost(self, reason)
		self.factory.number_of_connections -= 1

	def connectionMade(self):
		self.timeout=self.factory.timeout
		print "Une connexion etablie "+str(self.factory.number_of_connections+1)
		if self.factory.number_of_connections >= self.factory.max_connections:
			self.error(self.action.TOO_MANY_PEOPLE)
			self.factory.number_of_connections += 1
			return
		self.factory.number_of_connections += 1
		self.timeout_deferred = reactor.callLater(MServeurProtocol.timeout, self.on_timeout)
	
	def dataReceived(self, data):
		#policy-file-request
		if(data.startswith('<policy-file-request/>')):
			print "Demande de policy"
			self.transport.write(open(self.factory.policy_file, 'rt').read())
			self.transport.loseConnection()
			return
		#PyGram
		try:
			self.decoder = pyamf.get_decoder(self.encoding,data,pyamf.get_context(self.encoding))
			i=0
			#on lit le flux jusqu'a qu'il soit vide
			while(i==i):
				try:
					message=self.decoder.readElement()
					i=i+1
					print "recu:",message
					if(not message.isCorrect()):
						raise AttributeError,"Fonction appelee incorrecte"
					#appel dynamique de fonction de actions
					if(self.identified or message.action=="Identification"):
						getattr(self.action,message.action)(*message.parameters)
				except(AttributeError,TypeError,pyamf.DecodeError),detail:
					print "Message incorrecte : ",message," : ",detail
		except(pyamf.EOStream):
			print "fin du flux",i
			
	def error(self,action,parameters=()):
		self.send(action,parameters)
		self.close()
		
	def send(self,action,parameters=()):
		
		message=PyGram()
		message.action=action
		message.parameters=parameters
		print "send : ",message
		self.encoder = pyamf.get_encoder(self.encoding)
		self.stream = self.encoder.stream
		self.encoder.writeElement(message)	
		data=self.stream.getvalue()
		self.transport.write(data)
		self.stream.truncate()
		self.stream.close()
	def on_timeout(self):
		print "timeout"
		self.timeout_deferred=None
		self.error(self.action.TIMEOUT)
	def end_timeout(self):
		if self.timeout_deferred:
				self.timeout_deferred.cancel()
				self.timeout_deferred=None
		
class Actions:
	BAD_IDENTIFICATION="bad_identification"
	TIMEOUT="timeout"
	TOO_MANY_PEOPLE="too_many_people"
	GOOD_IDENTIFICATION="good_identification"
	ENTER_ROOM="enter_room"
	PEOPLE_OUT="people_out"
	PEOPLE_IN="people_in"
	ALREADY_CONNECTED="already_connected"
	SEND="send"
	ECHEC_SEND="echec_send"
	
	def __init__(self,protocol):
		self.protocol=protocol
		
	def Identification(self,idx,pwd):
		print "Identification"
		params = "idx="+str(idx)+"&id_pwd="+str(pwd)+"&port="+str(self.protocol.factory.port)
		data = urllib.urlopen(self.protocol.factory.serveur_php,params).read() 
		print data
		#verification de l'identification
		if(data=="ok"):
			if(not self.protocol.factory.clients.exists(idx)):
				print "identified"
				self.protocol.end_timeout()
				self.protocol.identified=True
				self.protocol.idx=idx
				self.protocol.rooms=MPrivateRoom()
				#ajout du socket dans MClients
				self.protocol.factory.clients.connect(self.protocol)
				self.__send(self.GOOD_IDENTIFICATION)
			else:
				self.__error(self.ALREADY_CONNECTED)
		else:
			self.__error(self.BAD_IDENTIFICATION,[idx,pwd])	
	def echo(self,message):
		self.__send("echo",[message])
		
	def goto(self,room):
		self.__send(self.ENTER_ROOM,[room,self.protocol.factory.rooms.enter(room,self.protocol.idx)])
		self.protocol.rooms.enter(room)
		self.__sendRoom(room,self.PEOPLE_IN,[room,self.protocol.idx])
		
	def leave(self,room):
		self.__sendRoom(room,self.PEOPLE_OUT,[room,self.protocol.idx])
		self.protocol.factory.rooms.leave(room,self.protocol.idx)
		self.protocol.rooms.leave(room)
		
	#envoi d'un utilisateur a un autre
	#TODO:empecher l'auto envoi ?
	def send(self,room,idx,message):
		if(self.protocol.rooms.isIn(room)):
			if(self.protocol.factory.clients.exists(idx)):
				destinataire=self.protocol.factory.clients.get(idx)
				if destinataire.rooms.isIn(room):
					destinataire.send(self.SEND,[room,self.protocol.idx,message])#on lui fournit le nom du salon+ idx expediteur
					return
		self.__send(self.ECHEC_SEND,[room,idx,message])
		
	#envoi d'un utilisateur a un salon
	def sendRoom(self,room,message):
		if(self.protocol.rooms.isIn(room)):
			
			self.__sendRoom(room,self.SEND,[room,self.protocol.idx,message])
		else:
			self.__send(self.ECHEC_SEND,[room,message])
			
	def __sendRoom(self,room,action,parameters=[]):
		#on veille a ce qu'il soit bien dans le salon
		if(self.protocol.rooms.isIn(room)):
			clients=self.protocol.factory.rooms.get(room)
			for p in clients:
				client=self.protocol.factory.clients.get(p)
				if(not client is None):
					client.send(action,parameters)
			
	def __send(self,action,parameters=[]):
		#print "send:",action,parameters
		self.protocol.send(action,parameters)
	def __error(self,action,parameters=[]):
		self.protocol.error(action,parameters)
'''
struct : clients[idx]=MServeurProtocol
'''
class MClients:
	def __init__(self):
		self.clients={}
	def connect(self,socket):
		idx=socket.idx
		self.clients[idx]=socket
	def disconnect(self,socket):
		idx=socket.idx
		if(self.clients.has_key(idx)):
			del self.clients[idx]
	def exists(self,idx):
		return idx in self.clients.keys()
	def get(self,idx):
		if(self.clients.has_key(idx)):
			return self.clients[idx]
		else:
			return None
'''
struct : rooms=[salon1,salon2,salon3]
'''	
class MPrivateRoom:
	def __init__(self):
		self.rooms=[]
	def enter(self,name):
		if(not self.isIn(name)):
			self.rooms.append(name)
	def leave(self,name):
		if(self.isIn(name)):
			self.rooms.remove(name)
	def isIn(self,name):
		return name in self.rooms
			
'''
struct : rooms[name]=[idx1,idx2,idx3]
'''
class MRooms:
	def __init__(self):
		self.rooms={}

	def enter(self,name,idx):
		if(not self.rooms.has_key(name)):
			self.rooms[name]=[idx]
		if(not idx in self.rooms[name]):
			self.rooms[name].append(idx)
		return self.rooms[name]
	def leave(self,name,idx):
		if(self.rooms.has_key(name)):
			if(idx in self.rooms[name]):
				self.rooms[name].remove(idx)
				if(len(self.rooms[name])==0):
					del self.rooms[name]
					#supression sur le serveur pour plus de dynamisme
					print "supression de :",str(name)
					#TODO:urlencode sur php
					params = "port="+str(MServeur.port)+"&room="+str(urllib.quote(name))
					urllib.urlopen(MServeur.serveur_php,params)
	def get(self,name):
		if(self.rooms.has_key(name)):
			return self.rooms[name]
		else:
			return []
class MServeur(Factory):
	protocol = MServeurProtocol
	max_connections = 1000
	serveur_php = ""
	port = 8000
	def __init__(self,connections=1000,policy_file='socket-policy.xml',serveur_php='http://192.168.0.3/php/amfphp/services/admin_mserveur.php',timeout=15,port=8080):
		#classmaping
	#	pyamf.register_package(PyGram,"mymyoux.io.mserveur.mapping.PyGram")
	#	pyamf.register_class(test,"mymyoux.io.MServeur.test")
		pyamf.register_class(PyGram,"mymyoux.io.mserveur.mapping.PyGram")
		#initialisation des variables
		self.number_of_connections = 0
		MServeur.max_connections=connections
		self.policy_file=policy_file
		MServeur.serveur_php=serveur_php
		self.clients=MClients()
		self.rooms=MRooms()
		self.timeout=timeout
		MServeur.port=port
		
		self.interval=30

		print "Lancement du serveur\nNombres de cliens maximum accepte : ",self.max_connections
		params = "port="+str(self.port)+"&action=init"
		urllib.urlopen(self.serveur_php,params)
		self.updateData()
		
	def updateData(self):
		print "udpate ",self.interval
		params = "port="+str(self.port)+"&clients="+str(self.number_of_connections)+"&max_clients="+str(self.max_connections)+"&update="+str(self.interval)
		try:
			urllib.urlopen(self.serveur_php,params)
		except (IOError):
			print "Attention SERVEUR PHP injoignable"
		self.timeout_deferred = reactor.callLater(self.interval, self.updateData)

class PyGram:
	def __init(self):
		self.action=""
		self.parameters=[]
	def __str__(self):
		return "[PyGram action=\""+self.action+"\" parameters=\""+str(self.parameters)+"\"]"
	def isCorrect(self):
		return not self.action.startswith("__")
class test:
	def __init__(self):
		self.essasi='ou'
		self.t=3
		print "test"
	
class essai:
	ess="13"
	def __init__(self):
		print "meuh"
'''if __name__ == '__main__':
	
	reactor.listenTCP(8000, TimerFactory())
	reactor.listenTCP(843, SocketPolicyFactory('socket-policy.xml'))
	reactor.run()
'''
def info(object, spacing=10, collapse=1):
	"""Print methods and doc strings.
    
    Takes module, class, list, dictionary, or string."""
	methodList = [method for method in dir(object) if callable(getattr(object, method))]
	processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)
	print "\n".join(["%s %s" % (method.ljust(spacing),processFunc(str(getattr(object, method).__doc__))) for method in methodList])

